﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Mvc;

using ProjectBase.Core;
using ProjectBase.Core.Service;
using ProjectBase.Core.Web;
using ProjectBase.Mvc;

using $prjsn$.Common;
using $prjsn$.Common.Business.Services;
using $prjsn$.Common.Models;

namespace $safeprojectname$
{
    /// <summary>
    ///     I. Provides access to the Controllers and Actions based on the Menu.config file settings and CurrentRole.
    ///     The Menu.config is therefore used twice! Firstly for roles granting, secondly (NavigationFilterAttribute)
    ///     To be reused for hidding inaccessible controllers and actions. Time and bugs saving solution.
    ///     II. On action executed also checks, if the Model.MasterModel.IsReadOnly - then every action which is in
    ///     the WriteAction list is removed (models from actions and buttons).
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = true)]
    public class AccessFilterAttribute : ActionFilterAttribute, IAuthorizationFilter, IPublishingReporter
    {
        #region IAuthorizationFilter Members
        /// <summary>
        ///     Checks the Menu.xml file for access
        ///     and also the Access.Deny setting on selected Actions
        /// </summary>
        /// <param name = "filterContext"></param>
        public virtual void OnAuthorization(AuthorizationContext filterContext)
        {
            var controller = filterContext.Controller as IWebController<IViewModel>;
            var controllerName = filterContext.RouteData.Values[Str.Common.Controller].ToString();
            var actionName = filterContext.RouteData.Values[Str.Common.Action].ToString();

            if (filterContext.ParentActionViewContext.Is())
            {
                return; // Partial View must be evaluated according to the parent settings
            }

            try
            {
                var isAllowed = SecurityManager // is there any role which allowes acces to this
                    .GrantedControllers
                    .Where(gc => gc.ControllerName.IsEqual(controllerName))
                    .Any(gc => gc.Roles.Any(r => r.UseDenied
                                                        ? !r.DeniedActions.Contains(actionName,
                                                            StringComparer.OrdinalIgnoreCase)
                                                        : r.GrantedActions.Contains(actionName,
                                                            StringComparer.OrdinalIgnoreCase)));
                var isReadOnly = SecurityManager // are all provided roles for this controller marked as readonly?
                    .GrantedControllers
                    .Where(gc => gc.ControllerName.IsEqual(controllerName))
                    .All(gc => gc.Roles.All(r => r.IsReadOnly));
                var isWriteAction = SecurityManager.IsWriteAction(controllerName, actionName);


                if (controller.Is())
                {
                    controller.Model.MasterModel.IsReadOnly = isReadOnly;
                }
                if (isAllowed // only allowed
                    && !(isReadOnly && isWriteAction)) // and write actions only for NOT readOnly
                {
                    return; // access is granted - other processing informs about reason for disallowing
                }
            }
            catch (Exception ex)
            {
                this.PublishFatal("Authorization failed. Fatal error, examine stack and contact responsible", ex);
            }
            

            // handle unauthorized access or exceptional state

            if (controller.Is() && controller.Model.Is() && controller.Model.Messages.Is())
            {
                controller.Model.Messages.Clear();
                controller.PublishError(Str.Messages.NoAccess4
                    , SecurityManager.User.LastName
                    , SecurityManager.User.FirstName
                    , controllerName
                    , actionName);
            }

            Factory.CreateInstance<IUserFacade>().Clear();
            filterContext.Result = new HttpUnauthorizedResult();
            DenyAccess(filterContext);
        }
        #endregion

        #region OnAction
        /// <summary>
        ///     If Model is set to ReadOnly removes all the Write actions
        /// </summary>
        /// <param name = "filterContext"></param>
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            this.PublishInfo(() => "OnActionExecuted--started-AccessFilter");
            ClearWriteActionList(filterContext);
            ClearProhibitedActionAndButton(filterContext);
            this.PublishInfo(() => "OnActionExecuted-finished-AccessFilter");
        }
        #endregion OnAction

        #region Handle IModels if ReadOnly
        public static void ClearWriteActionList(ControllerContext context)
        {
            var controller = context.Controller as IWebController<IViewModel>;
            if (controller.IsNull())
            {
                return;
            }

            // if NOT readonly - get out of here
            if (!controller.Model.MasterModel.IsReadOnly)
            {
                return;
            }

            // READONLY - remove write actions
            if (Global.Settings.WriteActions.IsNotEmpty())
            {
                foreach (var wa in Global.Settings.WriteActions)
                {
                    if (wa.Controller.IsNotEmpty())
                    {
                        controller.RemoveAction(wa.Action, wa.Controller);
                        controller.RemoveButton(wa.Action, wa.Controller);
                    }
                    else
                    {
                        controller.RemoveAction(wa.Action);
                        controller.RemoveButton(wa.Action);
                    }
                }
            }

            var model = controller.Model as IEntityModel<IPersistentObject, IFilter>;
            var hasListModel = model.Is() && model.ListModel.Is();
            if (hasListModel)
            {
                model.ListModel.IsReadOnly = model.MasterModel.IsReadOnly;
            }
        }

        public static void ClearProhibitedActionAndButton(ControllerContext context)
        {
            var controller = context.Controller as IWebController<IViewModel>;
            if (controller.Is())
            {
                ClearProhibitedActions(controller);
                ClearProhibitedButtons(controller);
                ClearListActions(controller);
            }
        }

        protected static void ClearListActions(IWebController<IViewModel> controller)
        {
            var model = controller.Model as IEntityModel<IPersistentObject, IFilter>;
            var hasListModel = model.Is() && model.ListModel.Is();
            if (!hasListModel)
            {
                return;
            }

            var canEdit = !controller.IsActionProhibited(Str.Actions.Edit, controller.ControllerName);
            var canDelete = !controller.IsActionProhibited(Str.Actions.NoView.Delete, controller.ControllerName);
            var showDetail = !controller.IsActionProhibited(Str.Actions.Detail, controller.ControllerName);

            model.ListModel.ShowDetailAction = showDetail;

            if (canEdit && canDelete)
            {
                return; // no restrictions needed
            }

            var isSpecial = model.ListModel.RowRenderMode == RowRenderMode.Checkbox
                || model.ListModel.RowRenderMode == RowRenderMode.Radio;

            if (isSpecial)
            {
                return; // no edit navigation rendered, but leading input field
            }

            // partial editor / remover
            if (canEdit)
            {
                model.ListModel.RowRenderMode = RowRenderMode.Editonly;
                return;
            }
            if (canDelete)
            {
                model.ListModel.RowRenderMode = RowRenderMode.Deleteonly;
                return;
            }

            model.ListModel.RowRenderMode = RowRenderMode.Readonly;
        }

        protected static void ClearProhibitedActions(IWebController<IViewModel> controller)
        {
            var prohibitedActions = new List<IActionItem>();
            foreach (var action in controller.Model.MasterModel.ActionsModel.Actions)
            {
                var controllerName = action.ControllerName ?? controller.ControllerName;
                if (controller.IsActionProhibited(action.ActionName, controllerName))
                {
                    prohibitedActions.Add(action);
                }
            }
            foreach (var action in prohibitedActions)
            {
                controller.RemoveAction(action.ActionName);
            }
        }

        protected static void ClearProhibitedButtons(IWebController<IViewModel> controller)
        {
            var prohibitedButtons = new List<IButtonItem>();
            foreach (var button in controller.Model.MasterModel.ButtonsModel.Buttons)
            {
                var controllerName = button.FormController ?? controller.ControllerName;
                if (controller.IsActionProhibited(button.ButtonName, controllerName))
                {
                    prohibitedButtons.Add(button);
                }
            }
            var submit = controller.Model.MasterModel.ButtonSubmit;
            if (submit.Is()
                && controller.IsActionProhibited(submit.ButtonName, submit.FormController ?? controller.ControllerName))
            {
                controller.Model.MasterModel.ButtonSubmit = null;
            }
            foreach (var action in prohibitedButtons)
            {
                controller.RemoveButton(action.ButtonName);
            }
        }
        #endregion Handle IModels if ReadOnly

        #region protected DenyAccess transfer
        protected virtual void DenyAccess(AuthorizationContext filterContext)
        {
            var controllerName = (string)filterContext.RouteData.Values[Str.Common.Controller];
            var action = (string)filterContext.RouteData.Values[Str.Common.Action];
            var controller = filterContext.Controller as IWebController<IViewModel>;

            // View result
            if (controller.Is())
            {
                // Model
                var model = Factory.CreateInstance<IExceptionModel>();
                model.MasterModel.ControllerName = controllerName;
                model.MasterModel.CurrentAction = action;
                model.Messages = new Messages();

                // Messages
                foreach (var message in controller.Messages)
                {
                    model.Messages.Add(message);
                }
                controller.Messages.Clear();

                var result = new ViewResult
                {
                    ViewData = new ViewDataDictionary<IExceptionModel>(model),
                    ViewName = Str.WebControls.NoAccess,
                };
                filterContext.Result = result;

            }
            filterContext.Session().Clear();
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 401; // not authorized
        }
        #endregion protected DenyAccess transfer
    }
}